﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using Framework;
using ProtoBuf;
using System.Collections;

namespace SchemaMonitor.json
{
    [DataContract]
    [ProtoInclude(11, typeof(CFieldBlob))]
    [ProtoInclude(12, typeof(CFieldBool))]
    [ProtoInclude(13, typeof(CFieldDate))]
    [ProtoInclude(14, typeof(CFieldSingle))]
    [ProtoInclude(15, typeof(CFieldInt))]
    [ProtoInclude(16, typeof(CFieldLong))]
    [ProtoInclude(17, typeof(CFieldString))]
    [ProtoInclude(18, typeof(CFieldDouble))]
    [ProtoInclude(19, typeof(CFieldDecimal))]
    [ProtoInclude(21, typeof(CFieldPointer))]
    [ProtoInclude(22, typeof(CFieldComposite))]
    public abstract class CField
    {
        //Members         
        [DataMember(Order =1)]
        public int TagId { get; set; }

        [NonSerialized]
        private CNodeBasic _parent;

        //Constructors
        protected CField() { }
        protected CField(CNodeBasic parent) { _parent = parent; }
        protected CField(CNodeBasic parent, CTag tag) : this(parent, tag.TagId) { }
        protected CField(CNodeBasic parent, int tagId)
        {
            this.TagId = tagId;
        }
        static CField() { ProtoBuf.Serializer.PrepareSerializer<CField>(); }


        //MustOverride
        protected abstract IList InitList();
        protected abstract IList InitList(object[] array);

        public string Path
        {
            get
            {
                if (null == _parent)
                    return Tag.TagName;

                return String.Concat(_parent.Path, ".", Tag.TagName);
            }
        }

        //List
        public    abstract IList List { get; set; }

        //Array
        public object[] Values
        {
            get
            {
				if (List == null)
					return null;
                return List.Cast<object>().ToArray();
            }
            set
            {
                List = InitList(value);
            }
        }

        public CNodeBasic Parent { get {  return _parent; } }
        internal  void SetParent(CNodeBasic p)
        {
            this._parent = p;
        }

        //Object (either: a List; or first in List)
        public string String {  get { return (string)Value;   } set { Value = value; } }
        public DateTime Date {  get { return (DateTime)Value; } set { Value = value; } }
        public long     Long {  get { return (long)Value;     } set { Value = value; } }
        public int       Int {  get { return (int)Value;      } set { Value = value; } }
        public bool     Bool {  get { return (bool)Value;     } set { Value = value; } }
        public double Double {  get { return (double)Value;   } set { Value = value; } }
        public object Value
        {
            get
            {
                if (Tag.TagIsList)
                    return List;

                var v = List;
                if (null == v)
                    return null;
                if (0 == v.Count)
                    return null;
                return v[0];
            }
            set
            {
                if (Tag.TagIsList)
                {
                    if (value is IList)
                        List = (IList)value;
                    else if (value is object[])
                        Values = (object[])value;
                    else if (value is Array)
                        Values = ((Array)value).Cast<object>().ToArray();
                    else
                        ;
                    return;
                }

                var v = List;
                if (null == value)
                {
                    if (null == v)
                        return;
                    if (v.Count > 0)
                        v.Clear();
                }
                else
                {
                    if (null == v)
                    {
                        List = InitList();
                        v = List;
                    }
                    if (v.Count == 0)
                        v.Add(value);
                    else
                        v[0] = value;
                }
            }
        }

        //Helper
        public CNode AsPointer(CNodeList all) { return ((CFieldPointer)this).GetNode(all); }
        public CNodeList AsPointerList(CNodeList all) { return ((CFieldPointer)this).GetNodes(all); }
        public CNodeBasic AsComposite
		{
			get
			{
				if (null != Values && 0 != Values.Length)
					return (CNodeBasic)Values[0];
				return (CNodeBasic)Value;
			}
		}
        public List<CNodeBasic> AsCompositeList
		{
			get
			{

				return (List<CNodeBasic>)Value;
			}
		}
        public virtual string AsString
		{
			get
			{
				if (null == Value) return string.Empty;
				if (Value is IList)
				{
					IList list = (IList)Value;
					var type = "unknown";
					if (list.Count > 0)
					{
						var obj = list[0];
						type = obj.GetType().ToString();
						if (obj is CNodeBasic)
							type = ((CNodeBasic)obj).Type.TypeName;						
					}
					return CUtilities.CountSummary(list, type);
				}
				if (Value is CNodeBasic)
				{
					var node = (CNodeBasic)Value;
					return node.Name;
				}
				return Value.ToString();
			}
			set
			{ Value = value; }
		}
        public  bool AsBool {  get { return (bool)(Value??false); } set { Value = value; } }
		public List<long> AsLongList
		{
			get
			{
				return ((CFieldLong)this).Longs;
			}
			set
			{
				((CFieldLong)this).Longs = value;
			}
		}

		public long? AsLong
        {
            get
            {
                if (null == Value)
                    return null;
                if (Value is string)
                    if (Value.ToString().Contains("_"))
                        return null;
                try
                {
                    return Convert.ToInt64(Value);
                }
                catch (Exception ex)
                {
                    string s = ex.Message;
                    return null;
                }
            }
             set { Value = value; } }

		public int AsInteger
		{
			get
			{
				if (null == Value)
					return int.MinValue;
				try
				{
					return Convert.ToInt32(Value);
				}
				catch (Exception ex)
				{
					string s = ex.Message;
					return int.MinValue;
				}
			}
			set { Value = value; }
		}

		public override string ToString()
        {
            return string.Concat(Tag.TagName, "=", AsString);
        }
        public override int GetHashCode()
        {
            return ToString().GetHashCode();
        }


        //Property
        public CTag Tag {  get { return CTag.Cache.GetById(TagId); } }
        public byte[] AsBytes()
        {
            return CSerialise.Proto.Serialise_(this);
        }
        public string AsBase64()
        {
            return CBinary.ToBase64(AsBytes());
        }

		public static object ConvertWeirdArrays(object obj)
        {
			if (!(obj is Dictionary<string, object>))
				return obj;

			var d = (Dictionary<string, object>)obj;
			var list = new List<Dictionary<string, object>>();

			int key;
            foreach (var i in d)
            {
				//#1: all keys are int
				if (!int.TryParse(i.Key, out key))
					return obj;

				//#2: all values are arrays
                if (!(i.Value is object[]))
                    return d;

				//#3: Array-length is 1
				var arr = (object[])i.Value;
				if (arr.Length != 1)
					return obj;

				//#3: Array contains one of these
				var offset = new COffset(arr[0]);

				//Key matchs the offset
				if (key != offset.Offset)
					return d;

				//Length matches the string length
				if (offset.Length > 0)
					if (offset.Length != offset.Name.Length)
						;// return d;
				offset.Clean();
				
				list.Add(new Dictionary<string, object>(offset));
            }
			var arrList = new System.Collections.ArrayList(list);
			return arrList.ToArray();
        }

        //Factory
        public static CField Factory(KeyValuePair<string, object> pair, CNodeBasic parent, CUser cache)
        {
            return Factory(pair.Key, pair.Value, parent, cache);
        }
        public static CField Factory(string key, object value2, CNodeBasic parent, CUser cache)
        {
            var value = ConvertWeirdArrays(value2);

            //Special case
            CPage p = null;
            if (value is Dictionary<string, object>)
            {
                p = new CPage((Dictionary<string, object> )value);
                if (p.HasData)
                    value = p.Data;
            }
            if (value is object[])
            {
                var arr = (object[])value;
                if (arr.Length == 0)
                    return null;
            }
			/*
			if (value is List<Dictionary<string, object>>)
			{
				var list = ((List<Dictionary<string, object>>)value);
				CTag tag0 = parent.Type.GetOrCreateTag(key, list[0], cache);
				tag0.TagIsList = true;
				return FactoryIfCompositeList(parent, list, tag0, cache);
			}
			*/

			//Create the tag
			CTag tag = parent.Type.GetOrCreateTag(key, value, cache);
			if (null == tag)
				return null;
            CType t = tag.FieldType;
            

            //Object Array (TODO)
            if (value is object[])
                if (tag.FieldType.IsSystem)
                    return FactoryIfAtomic(parent, value, tag, cache);
                    //return FactoryIfAtomicList(parent, (object[])value, tag, cache);
                    //throw new Exception("Atomic list not supported yet");
                else
                    return FactoryIfCompositeList(parent, (object[])value, tag, cache);
    

            //Create the field (basic)
            if (t.IsSystem)
                return FactoryIfAtomic(parent, value, tag, cache);


            //Create the field (recursive)
            try
            {
                if (value is Dictionary<string, object>)
                {
                    var d = (Dictionary<string, object>)value;
                    return FactoryIfComposite(parent, d, tag, cache);
                }
                return Factory(key, "Error: " + value.GetType().ToString(), parent, cache);
            }
            catch (Exception ex)
            {
                return Factory(key, ex.Message, parent, cache);
            }
        }


        private static CField FactoryIfAtomic(CNodeBasic parent, object value, CTag tag, CUser c)
        {
            //Check type
            value = CSignature.JsonToNative(value);

            //Update tag with metadata (maintenance)
            if (parent.HasMeta || tag.Parent.HasMeta)
            {
                var m = parent.Metadata??tag.Parent.Metadata;
                if (m.Has(tag.TagName))
                {
                    if (String.IsNullOrEmpty(tag.TagDescription))
                    {
                        var mf = m[tag.TagName];
                        tag.TagName = mf.Name;
                        tag.TagDescription = mf.Description;
                        tag.Save();
                    }
                    //var t = CType.Cache.GetOrCreate(tag.Path, )
                    //tag.TagIsTypeId =
                }
            }

            //Convert bigint to string
            /*
            if (value.ToString().Contains("_"))
            {
                tag.TagIsTypeId = CType.Cache.GetOrCreate_System(typeof(string)).TypeId;
                tag.Save();
            }*/

            //Convert int to big-int, as per metadata
            var s = value?.ToString();
            if (tag.FieldType.TypeApiPath == (typeof(long)).ToString())
                if (!s.Contains("_") && !s.Contains(":"))
                    value = Convert.ToInt64(value);
            if (tag.FieldType.TypeApiPath == (typeof(Single)).ToString())
                value = Convert.ToSingle(value);
            var sig = new CSignature(value.GetType());
			if (value is object[])
				sig = new CSignature(((object[])value)[0].GetType());
            
            //Known type
            var t = tag.FieldType;
            var match = t.Signatures.System.GetByHash(sig);
            if (match.Count == 1)
            {
                var expect = match[0];
                if (expect.SigHash != sig.SigHash)
                    CLog.LogEx(c, "Tag: " , tag.Path , ": Expected type " , expect.Type.TypeName , "; parsed as type: ");
            }


            if (sig.Is(typeof(string)))   return new CFieldString(parent, tag, value);
            if (sig.Is(typeof(long)))     return new CFieldLong(parent, tag, value);
            if (sig.Is(typeof(Int64)))    return new CFieldLong(parent, tag, value);
            if (sig.Is(typeof(DateTime))) return new CFieldDate(parent, tag, value);
            if (sig.Is(typeof(bool)))     return new CFieldBool(parent, tag, value);
            if (sig.Is(typeof(int)))      return new CFieldInt(parent, tag, value);
            if (sig.Is(typeof(double)))   return new CFieldDouble(parent, tag, value);
            if (sig.Is(typeof(Single)))   return new CFieldSingle(parent, tag, value);
            if (sig.Is(typeof(decimal)))  return new CFieldDecimal(parent, tag, value);
            if (sig.Is(typeof(byte[])))   return new CFieldBlob(parent, tag, value);

            throw new Exception("Not implemented: Field Type " + sig.SigDescription);
        }
        private static CField FactoryIfComposite(CNodeBasic p, Dictionary<string, object> d, CTag tag, CUser c)
        {
            CType t = tag.FieldType;

            if (!t.HasId || !CNodeRoot.HasId_(d) || d.Keys.Count <=2)
                return new CFieldComposite(p, tag, d, c);

            var n = CNodeRoot.PersistJson(t, d, c);
            return n.AsPointer(p, tag);
        }
        private static CField FactoryIfAtomicList(CNodeBasic p, object[] array, CTag tag, CUser c)
        {
            if (array.Length > 0)
            {
                var a = new CDict(array[0]);
                var t = CType.Cache.GetOrCreate_System(a.GetType());
                    return FactoryIfAtomic(p, array, tag, c);
            }
            return null;
        }
        private static CField FactoryIfCompositeList(CNodeBasic p, object[] array, CTag tag, CUser c)
        {
            CType t = tag.FieldType;

            //Composite array
            if (!t.HasId)
                return new CFieldComposite(p, tag, array, c);
            
            if (array.Length > 0)
            {
                var a = new CDict(array[0]);
                if (!CNodeRoot.HasId_(a) || tag.TagName == "tags")
                    return new CFieldComposite(p, tag, array, c);
            }

            //Store, get ids
            var dbIds = new List<Guid>(array.Length);
            foreach (Dictionary<string, object> i in array)
                dbIds.Add(CNodeRoot.PersistJson(t, i, c).NodeGuid);

            //return those ids
            return new CFieldPointer(p, tag, dbIds);
        }
    }
}
